package com.start.workflow.core.manage;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.start.common.exceptions.StartBusException;
import com.start.common.mybaitsplus.StartMBColumnTools;
import com.start.workflow.act.StartDeployHistory;
import com.start.workflow.act.StartProcDef;
import com.start.workflow.act.StartProcTaskDef;
import com.start.workflow.common.enums.StartActConf;
import com.start.workflow.common.enums.StartActUserTaskConf;
import com.start.workflow.core.StartActTools;
import com.start.workflow.core.mappers.StartActReModelExtMapper;
import com.start.workflow.core.service.StartActCategoryService;
import com.start.workflow.core.service.StartActModelConfigService;
import com.start.workflow.entity.StartActCategory;
import com.start.workflow.entity.StartActModelConfig;
import com.start.workflow.entity.StartActReModelExt;
import com.start.workflow.model.StartProcessModel;
import com.start.workflow.model.StartProcessModelConfigDetail;
import com.start.workflow.vo.StartActCategoryModes;
import org.activiti.bpmn.constants.BpmnXMLConstants;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.UserTask;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.impl.util.json.XML;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.Model;
import org.activiti.engine.repository.ProcessDefinition;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.xml.stream.XMLStreamException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class StartActModelManage {
    private static final String BPMN20_XML = ".bpmn20.xml";
    String DEFAULT_BPMN_XML_TEMPLATE = " <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
            "<definitions xmlns=\"http://www.omg.org/spec/BPMN/20100524/MODEL\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:bpmndi=\"http://www.omg.org/spec/BPMN/20100524/DI\" xmlns:omgdc=\"http://www.omg.org/spec/DD/20100524/DC\" xmlns:omgdi=\"http://www.omg.org/spec/DD/20100524/DI\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:activiti=\"http://activiti.org/bpmn\" targetNamespace=\"http://www.activiti.org/test\">\n" +
            "<process id=\"${modelKey}\" name=\"${modelName}\" isExecutable=\"true\" >\n" +
            "    <startEvent id=\"startevent1\" name=\"Start\" />\n" +
            "    <userTask id=\"usertask1\" name=\"usertask1审批\">\n" +
            "      <documentation>usertask1审批</documentation>\n" +
            "    </userTask>\n" +
            "    <userTask id=\"usertask2\" name=\"usertask2审批\" />\n" +
            "    <endEvent id=\"endevent1\" name=\"End\" />\n" +
            "    <sequenceFlow id=\"flow1\" name=\"flow1\" sourceRef=\"startevent1\" targetRef=\"usertask1\">\n" +
            "      <documentation>flow1连线</documentation>\n" +
            "    </sequenceFlow>\n" +
            "    <sequenceFlow id=\"flow2\" name=\"flow2\" sourceRef=\"usertask1\" targetRef=\"usertask2\">\n" +
            "      <documentation>usertask1审批出线</documentation>\n" +
            "    </sequenceFlow>\n" +
            "    <sequenceFlow id=\"flow3\" sourceRef=\"usertask2\" targetRef=\"endevent1\" />\n" +
            "  </process>\n" +
            "  <bpmndi:BPMNDiagram id=\"BPMNDiagram_daling\">\n" +
            "    <bpmndi:BPMNPlane id=\"BPMNPlane_daling\" bpmnElement=\"test_qxj\">\n" +
            "      <bpmndi:BPMNEdge id=\"BPMNEdge_flow3\" bpmnElement=\"flow3\">\n" +
            "        <omgdi:waypoint x=\"248\" y=\"277\" />\n" +
            "        <omgdi:waypoint x=\"248\" y=\"352\" />\n" +
            "      </bpmndi:BPMNEdge>\n" +
            "      <bpmndi:BPMNEdge id=\"BPMNEdge_flow2\" bpmnElement=\"flow2\">\n" +
            "        <omgdi:waypoint x=\"248\" y=\"147\" />\n" +
            "        <omgdi:waypoint x=\"248\" y=\"222\" />\n" +
            "        <bpmndi:BPMNLabel>\n" +
            "          <omgdc:Bounds x=\"232\" y=\"181\" width=\"25\" height=\"14\" />\n" +
            "        </bpmndi:BPMNLabel>\n" +
            "      </bpmndi:BPMNEdge>\n" +
            "      <bpmndi:BPMNEdge id=\"BPMNEdge_flow1\" bpmnElement=\"flow1\">\n" +
            "        <omgdi:waypoint x=\"248\" y=\"45\" />\n" +
            "        <omgdi:waypoint x=\"248\" y=\"92\" />\n" +
            "        <bpmndi:BPMNLabel>\n" +
            "          <omgdc:Bounds x=\"256\" y=\"56\" width=\"25\" height=\"14\" />\n" +
            "        </bpmndi:BPMNLabel>\n" +
            "      </bpmndi:BPMNEdge>\n" +
            "      <bpmndi:BPMNShape id=\"BPMNShape_startevent1\" bpmnElement=\"startevent1\">\n" +
            "        <omgdc:Bounds x=\"230\" y=\"10\" width=\"35\" height=\"35\" />\n" +
            "        <bpmndi:BPMNLabel>\n" +
            "          <omgdc:Bounds x=\"235\" y=\"-14\" width=\"25\" height=\"14\" />\n" +
            "        </bpmndi:BPMNLabel>\n" +
            "      </bpmndi:BPMNShape>\n" +
            "      <bpmndi:BPMNShape id=\"BPMNShape_endevent1\" bpmnElement=\"endevent1\">\n" +
            "        <omgdc:Bounds x=\"230\" y=\"352\" width=\"35\" height=\"35\" />\n" +
            "        <bpmndi:BPMNLabel>\n" +
            "          <omgdc:Bounds x=\"238\" y=\"387\" width=\"20\" height=\"14\" />\n" +
            "        </bpmndi:BPMNLabel>\n" +
            "      </bpmndi:BPMNShape>\n" +
            "      <bpmndi:BPMNShape id=\"BPMNShape_usertask1\" bpmnElement=\"usertask1\">\n" +
            "        <omgdc:Bounds x=\"195\" y=\"92\" width=\"105\" height=\"55\" />\n" +
            "      </bpmndi:BPMNShape>\n" +
            "      <bpmndi:BPMNShape id=\"BPMNShape_usertask2\" bpmnElement=\"usertask2\">\n" +
            "        <omgdc:Bounds x=\"195\" y=\"222\" width=\"105\" height=\"55\" />\n" +
            "      </bpmndi:BPMNShape>\n" +
            "    </bpmndi:BPMNPlane>\n" +
            "  </bpmndi:BPMNDiagram>\n" +
            "</definitions>";


    @Autowired
    private RepositoryService repositoryService;


    @Autowired
    private StartActReModelExtMapper startActReModelExtMapper;

    @Autowired
    private StartActModelConfigService startActModelConfigService;
    @Autowired
    private StartActCategoryService startActCategoryService;


    public Page<StartProcessModel> pageModel(Page<StartProcessModel> page, Wrapper<StartProcessModel> wrapper) {
        return startActReModelExtMapper.pageModel(page, wrapper);
    }


    @Transactional
    public void saveModel(StartProcessModelConfigDetail startProcessModel) {
        StartProcessModel processModel = startProcessModel.getProcessModel();
        if (processModel == null) {
            throw new StartBusException("流程信息不能为空");
        }
        if (StringUtils.isAllBlank(processModel.getActModelCategory())) {
            throw new StartBusException("请选择流程类别");
        }
        if (StringUtils.isAllBlank(processModel.getActModelKey())) {
            throw new StartBusException("请输入流程模型key");
        }
        if (StringUtils.isAllBlank(processModel.getActModelKey())) {
            throw new StartBusException("请输入流程名称");
        }
        Model model = repositoryService.newModel();
        model.setCategory(processModel.getActModelCategory());
        model.setName(processModel.getActModelName());
        model.setKey(processModel.getActModelKey());
        model.setTenantId(processModel.getTenantId());
        repositoryService.saveModel(model);
        processModel.setId(model.getId());
        startActReModelExtMapper.insert(processModel);
        if (StringUtils.isAllBlank(processModel.getBpmnXml())) {
            processModel.setBpmnXml(DEFAULT_BPMN_XML_TEMPLATE.replace("${modelKey}", model.getKey()).replace("${modelName}", processModel.getActModelName()));
        }
        repositoryService.addModelEditorSource(model.getId(), processModel.getBpmnXml().getBytes(StandardCharsets.UTF_8));
        if (startProcessModel.getConfigMap() == null || startProcessModel.getConfigMap().isEmpty()) {
            return;
        }
        handlerModelConfig(processModel, startProcessModel.getConfigMap());

    }

    /**
     * 处理流程模型配置
     *
     * @param processModel   流程模型
     * @param modelConfigMap 模型表单配置
     */
    private void handlerModelConfig(StartProcessModel processModel, Map<String, Map<String, Map<String, StartActModelConfig>>> modelConfigMap) {
        if (modelConfigMap == null || modelConfigMap.isEmpty()) {
            throw new StartBusException("请配置流程表单，流程审批人等信息");
        }
        List<StartActModelConfig> configs = new ArrayList<>();
        for (Map<String, Map<String, StartActModelConfig>> value : modelConfigMap.values()) {
            for (Map<String, StartActModelConfig> val : value.values()) {
                configs.addAll(val.values());
            }
        }
        configs = configs.stream().filter(e -> {
            if (StringUtils.isAllBlank(e.getConfKey())) {
                return false;
            }
            if (StringUtils.isAllBlank(e.getConfType())) {
                return false;
            }
            if (StringUtils.isAllBlank(e.getActType())) {
                return false;
            }
            e.setId(null);
            e.setCreateTime(LocalDateTime.now());
            e.setCreateBy(null);
            e.setProcDeployId("");
            e.setActModelId(processModel.getId());
            e.setCreateBy(null);
            e.setUpdateTime(null);
            return true;
        }).collect(Collectors.toList());
        startActModelConfigService.removeByModelId(processModel.getId());
        startActModelConfigService.saveBatch(configs);
    }


    @Transactional(rollbackFor = Exception.class)
    public void updateModel(StartProcessModelConfigDetail startProcessModel) {
        StartProcessModel processModel = startProcessModel.getProcessModel();
        Model model = repositoryService.createModelQuery().modelId(processModel.getId()).singleResult();
        if (StringUtils.isNotBlank(processModel.getActModelName())) {
            model.setName(processModel.getActModelName());
        }
        if (StringUtils.isNotBlank(processModel.getActModelCategory())) {
            model.setCategory(processModel.getActModelCategory());
        }
        if (StringUtils.isNotBlank(processModel.getTenantId())) {
            model.setTenantId(processModel.getTenantId());
        }
        model.setVersion(model.getVersion() + 1);
        repositoryService.saveModel(model);
        processModel.setDeployState(0);
        startActReModelExtMapper.updateById(processModel);
        BpmnModel bpmnModel = null;
        if (StringUtils.isNotBlank(processModel.getBpmnXml())) {
            try {
                bpmnModel = StartActTools.getBpmnModelByXml(processModel.getBpmnXml());
                repositoryService.addModelEditorSource(processModel.getId(), processModel.getBpmnXml().getBytes(StandardCharsets.UTF_8));
            } catch (XMLStreamException e) {
                throw new StartBusException(e);
            }
        }
        if (bpmnModel == null) {
            return;
        }
        handlerModelConfig(processModel, startProcessModel.getConfigMap());
    }

    /**
     * 删除流程模型配置
     *
     * @param id
     */

    public void deleteModel(String id) {
        Model model = repositoryService.getModel(id);
        repositoryService.deleteModel(id);
        startActReModelExtMapper.deleteById(id);
        startActModelConfigService.removeByModelId(model.getId());

    }

    /**
     * 获取模型
     *
     * @param modelId
     * @return
     */

    public StartProcessModelConfigDetail getActModelConfigDetailByModel(String modelId) {
        Model model = repositoryService.createModelQuery().modelId(modelId).singleResult();
        return getActModelConfigDetailByModel(model);
    }

    /**
     * 获取流程模型配置
     *
     * @param procDefKey     流程模型定义key
     * @param loadFormOption
     * @return
     */

    public StartProcDef<StartProcTaskDef> getStartProcDefDetail(String procDefKey, boolean loadFormOption) {
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionKey(procDefKey).latestVersion().singleResult();
        if (processDefinition == null) {
            throw new StartBusException("未找到有效的流程请先发布流程procDefKey:" + procDefKey);
        }
        Map<String, Map<String, Map<String, StartActModelConfig>>> actModelConfigMap = startActModelConfigService.getActModelConfigMap(processDefinition.getDeploymentId());

        StartProcDef<StartProcTaskDef> startProcDef = new StartProcDef<>(processDefinition);
        Map<String, Map<String, StartActModelConfig>> modelDefConfigMap = actModelConfigMap.getOrDefault(StartActConf.CARBON_COPY.getActType(), new HashMap<>());
        Map<String, StartActModelConfig> modelConfigMap = modelDefConfigMap.getOrDefault(procDefKey, new HashMap<>());
        startProcDef.setProcConfig(modelConfigMap);
        if (modelConfigMap.containsKey(StartActConf.ACT_FORM_TABLE.getValue())) {
            StartActModelConfig startActModelConfig = modelConfigMap.get(StartActConf.ACT_FORM_TABLE.getValue());
            startProcDef.setProcFormConfig(startActModelConfig);
            if (loadFormOption) {
                JSONObject jsonObject = StartActFormManager.loadFormOption(startActModelConfig);
                startProcDef.setFormModelOption(jsonObject);
            }

        }
        Map<String, Map<String, StartActModelConfig>> userTaskConfigMap = actModelConfigMap.getOrDefault(StartActUserTaskConf.FORM_TABLE.getActType(), new HashMap<>());
        List<StartProcTaskDef> taskDefs = new ArrayList<>();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
        List<String> filterElements = new ArrayList<>();
        filterElements.add(BpmnXMLConstants.ELEMENT_TASK_USER);
        StartActTools.bpmnModelFindElement(bpmnModel, null, null, filterElements, null, (level, elementType, flowElement) -> {
            UserTask userTask = (UserTask) flowElement;
            StartProcTaskDef taskDef = new StartProcTaskDef(userTask);
            taskDefs.add(taskDef);
            if (!userTaskConfigMap.containsKey(userTask.getId())) {
                return true;
            }
            Map<String, StartActModelConfig> taskMap = userTaskConfigMap.get(userTask.getId());
            taskDef.setTaskConfig(taskMap);
            if (taskMap.containsKey(StartActUserTaskConf.FORM_TABLE.getValue())) {
                StartActModelConfig startActModelConfig = taskMap.get(StartActUserTaskConf.FORM_TABLE.getValue());
                taskDef.setTaskFormConfig(startActModelConfig);
                JSONObject jsonObject = StartActFormManager.loadFormOption(startActModelConfig);
                taskDef.setNodeFormOption(jsonObject);
            }
            return true;
        });
        startProcDef.setProcTasks(taskDefs);
        return startProcDef;
    }

    /**
     * 获取流程模型配置
     *
     * @param model
     * @return
     */

    public StartProcessModelConfigDetail getActModelConfigDetailByModel(Model model) {
        if (model == null) return null;
        StartActReModelExt startActReModelExt = startActReModelExtMapper.selectById(model.getId());
        StartProcessModelConfigDetail startProcessModel = new StartProcessModelConfigDetail(model, startActReModelExt);
        StartProcessModel processModel = startProcessModel.getProcessModel();
        byte[] modelEditorSource = repositoryService.getModelEditorSource(model.getId());
        if (modelEditorSource != null && modelEditorSource.length > 0) {
            String xmlStr = new String(modelEditorSource, StandardCharsets.UTF_8);
            processModel.setBpmnXml(xmlStr);
            String xml = xmlStr.replaceAll("bpmn2:", "");
            processModel.setBpmnJson(JSON.parseObject(XML.toJSONObject(xml).toString()));
        }
        Map<String, Map<String, Map<String, StartActModelConfig>>> modelConfigMap = startActModelConfigService.getActModelConfigMapByModelId(processModel.getId());
        startProcessModel.setConfigMap(modelConfigMap);
        return startProcessModel;
    }

    /**
     * 获取流程模型并分组
     *
     * @return
     */
    public List<StartActCategoryModes> getGroupModels() {
        List<StartActCategory> categories = startActCategoryService.list();
        QueryWrapper<StartProcessModel> wrapper = new QueryWrapper<>();
        wrapper.gt(StartMBColumnTools.getColumn(StartProcessModel::getDeployVersion), 0);
        wrapper.orderByAsc(StartMBColumnTools.getColumn(StartProcessModel::getSort));
        List<StartProcessModel> allModel = startActReModelExtMapper.listModel(wrapper);
        Map<String, List<StartProcessModel>> collect = allModel.stream().collect(Collectors.groupingBy(StartProcessModel::getActModelCategory));
        List<StartActCategoryModes> startActCategoryModes = new ArrayList<>();
        for (StartActCategory item : categories) {
            List<StartProcessModel> modelVos = collect.getOrDefault(item.getActCategoryCode(), null);
            startActCategoryModes.add(StartActCategoryModes.create(item, modelVos));
        }
        return startActCategoryModes;
    }


    @Transactional
    public void deploymentModel(String modelId) {
        Model model = repositoryService.createModelQuery().modelId(modelId).singleResult();
        if (model == null) throw new StartBusException("未找到流程模型");
        byte[] modelEditorSource = repositoryService.getModelEditorSource(modelId);
        if (modelEditorSource == null || modelEditorSource.length < 1) {
            throw new StartBusException("未定流程内容,请先设计流程。");
        }
        BpmnModel bpmnModel;
        try {
            bpmnModel = StartActTools.getBpmnModelByBytes(modelEditorSource);
        } catch (XMLStreamException e) {
            e.printStackTrace();
            throw new StartBusException("获取流程图失败请检查是否配置流程图");
        }
        Deployment deploy = repositoryService.createDeployment()
                .key(model.getKey())
                .category(model.getCategory())
                .name(model.getName())
                .addBpmnModel(model.getKey() + BPMN20_XML, bpmnModel)
                .deploy();
        if (deploy == null || StringUtils.isEmpty(deploy.getId())) throw new StartBusException("流程模型部署失败");
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(deploy.getId()).singleResult();
        model.setVersion(processDefinition.getVersion());
        repositoryService.saveModel(model);
        startActReModelExtMapper.updateModelDeploy(model.getId(), processDefinition.getVersion());
        List<StartActModelConfig> actModelConfig = startActModelConfigService.getActConfigsByModelId(model.getId());
        for (StartActModelConfig config : actModelConfig) {
            config.setId(null);
            config.setProcDeployId(deploy.getId());
            config.setCreateTime(LocalDateTime.now());
        }
        startActModelConfigService.saveBatch(actModelConfig);
    }

    public List<StartDeployHistory> getModelDeployHistory(String defKey) {
        List<ProcessDefinition> definitions = repositoryService.createProcessDefinitionQuery().processDefinitionKey(defKey).orderByProcessDefinitionVersion().desc().list();
        return StartDeployHistory.create(definitions);

    }

    /**
     * 获取流程模型信息
     *
     * @param modeId
     * @return
     */
    public StartProcessModel getActModelInfo(String modeId) {
        return startActReModelExtMapper.getModelInfo(modeId);
    }
}
